package com.school.fuyuan.common.exception;


import com.school.fuyuan.business.constants.InfoConstants;
import com.school.fuyuan.common.message.RequestHead;
import com.school.fuyuan.common.message.RequestMsg;
import com.school.fuyuan.common.message.ResponseHead;
import com.school.fuyuan.common.message.ResponseMsg;
import com.school.fuyuan.common.util.JsonUtils;
import com.school.fuyuan.common.util.RequestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import org.springframework.dao.DataAccessException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author zrhong
 * @version 1.0
 * @date 2019/3/3 20:40
 * @description
 */
@ControllerAdvice
public class CommonExceptionHandler {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final boolean isActiveDev;

    public CommonExceptionHandler(Environment environment) {
        this.isActiveDev = "dev".equals(environment.getProperty("spring.profiles.active"));
    }

    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    @SuppressWarnings("unchecked")
    public ResponseMsg commonErrorHandler(HttpServletRequest req, Exception e) {

        ResponseMsg responseMsg = new ResponseMsg();
        ResponseHead responseHead = ResponseHead.buildFailedHead();
        responseMsg.setHead(responseHead);

        //Controller入参为RequestMsg时，在Aop切面中自动暂存requestId到request
        //Json解析异常时的requestId的来源请查看com.aspire.eab.common.configuration.converter.MsgObjectMapper
//        responseHead.setRequestId((String) req.getAttribute("requestId"));

        Map<String, Object> requestMap = (Map<String, Object>) req.getAttribute("requestMap");
        //防止当前台请求参数是非法Json时，因不会进到
        //com.aspire.eab.common.aspect.WebLogAspect而没有requestMap导致空指针异常
        if (requestMap == null) {
            requestMap = new LinkedHashMap<>();
        }

        //请求信息读取及绑定到对象时的异常，此时不会进入com.aspire.eab.common.aspect.WebLogAspect
        if (e instanceof HttpMessageNotReadableException) {
            HttpMessageNotReadableException hmnre = (HttpMessageNotReadableException) e;
            responseHead.setRespCode(InfoConstants.INFO_PARAM_FAIL);
            responseHead.setRespDesc("请求入参解析失败，请参考解析异常检查您的请求再重试 : "
                    + hmnre.getMessage());//解析请求信息异常(如请求Json非法等)
        }

        //请求参数绑定到对象成功后，但校验失败的时异常，此时不会进入com.aspire.eab.common.aspect.WebLogAspect
        else if (e instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException argNotValidEx = (MethodArgumentNotValidException) e;

            Method method = argNotValidEx.getParameter().getMethod();

            //参数校验失败时，可直接获取各种校验注解上的提示信息并可直接拿到入参target从而得到requestId
            //因此，可以从此去掉controller层每个方法上用不到的@ParamsCheck和BindingResult ret
            BindingResult bindingResult = argNotValidEx.getBindingResult();

            handleArgNotValidException(req, responseHead, requestMap, method, bindingResult);

        } else if (e instanceof BindException) {

            BindingResult bindingResult = ((BindException) e).getBindingResult();

            handleArgNotValidException(req, responseHead, requestMap, null, bindingResult);
        }

        //业务异常
        else if (e instanceof CommonRuntimeException) {
            CommonRuntimeException common = (CommonRuntimeException) e;
            responseHead.setRespCode(common.getCode());
            responseHead.setRespDesc(common.getMsg());
        }

        //数据库操作异常（sql有误等）
        else if (e instanceof DataAccessException) {
            responseHead.setRespCode(InfoConstants.INFO_DATABASE_FAIL);
            responseHead.setRespDesc("数据处理异常，服务器开小差了");
        }

        //其他异常
        else {
            responseHead.setRespCode("-1");
            responseHead.setRespDesc(e.getMessage());//其他异常直接输出
        }

        //本地开发时使用e.printStackTrace();打印
        String remoteAddr = RequestUtils.getIpAddress(req);
        if ("127.0.0.1".equals(remoteAddr) && isActiveDev) {
            e.printStackTrace();
        } else {
            logger.error("error responseMsg : ", e);
        }

        Long startTime = (Long) req.getAttribute("startTime");
        if (startTime == null) {
            startTime = System.currentTimeMillis();
        }

        requestMap.put("responseHead", responseHead);
        requestMap.put("responseData", responseMsg.getData());
        requestMap.put("costTime", System.currentTimeMillis() - startTime);

        if (logger.isErrorEnabled()) {
            logger.error("response : {} ", JsonUtils.toJsonStr(requestMap));
        }

        return responseMsg;
    }

    private void handleArgNotValidException(HttpServletRequest req, ResponseHead responseHead,
                                            Map<String, Object> requestMap, Method method,
                                            BindingResult bindingResult) {
        String url = req.getRequestURL().toString();
        String remoteAddr = req.getRemoteAddr();
        String httpMethod = req.getMethod();

        requestMap.put("remoteAddr", remoteAddr);
        requestMap.put("url", url);
        requestMap.put("httpMethod", httpMethod);
        if (method != null) {
            requestMap.put("classMethod", method.getDeclaringClass().getName() + "." + method.getName());
        }

        Object target = bindingResult.getTarget();
        if (target instanceof RequestMsg) {
            RequestHead requestHead = ((RequestMsg) target).getHead();
//            responseHead.setRequestId(requestHead.getRequestId());

            requestMap.put("requestHead", requestHead);
            requestMap.put("requestData", ((RequestMsg) target).getData());
        } else {
            requestMap.put("data", target);
        }

        responseHead.setRespCode("-1");
        responseHead.setRespDesc(bindingResult.getFieldError().getDefaultMessage());
    }

}
